#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "at_client.h"
#include "function.h"
#include "logger.h"
#include "device.h"
#include "serial.h"

DEFINE_LOGGER(AT, 128, LOG__TRACE);

#if 1
#define AT_RESP_END_OK                 "OK"
#define AT_RESP_END_ERROR              "ERROR"
#define AT_RESP_END_FAIL               "FAIL"
#define AT_END_CR_LF                   ""
#else
#define AT_RESP_END_OK                 "OK\r\n"
#define AT_RESP_END_ERROR              "ERROR\r\n"
#define AT_RESP_END_FAIL               "FAIL\r\n"
#define AT_END_CR_LF                   "\r\n"
#endif

#define AT_LINE_SIZE_MAX        (256)
static SERIAL_t _serial;
static struct at_urc_table _urc_table;
static char send_buf[128];
static char rev_buf[AT_LINE_SIZE_MAX];
static uint16_t rev_count = 0;

static uint32_t _at_vprintfln(const char *format, va_list args)
{
    int cmd_len = vsnprintf(send_buf, sizeof(send_buf), format, args);
    _serial->write(0,send_buf,cmd_len,NULL);
    return cmd_len;
}

static const struct at_urc *_at_get_urc_obj(char *line, uint32_t size)
{
    char *ptline = line;
    ptline[size] = '\0';

    for(int i = 0; i < _urc_table.urc_size; i++)
    {
        uint32_t prefix_len = strlen(_urc_table.urc[i].cmd_prefix);
        if((strstr(ptline,_urc_table.urc[i].cmd_prefix))&&(strstr(ptline+prefix_len,_urc_table.urc[i].cmd_suffix)))
        {
            return &(_urc_table.urc[i]);
        }
    }

    return NULL;
}

static int _at_get_response(at_response_t resp)
{
    const struct at_urc *urc;
    uint8_t ch = 0,last_ch = 0;

    if((resp == NULL)||(resp->buf == NULL))
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return -1;
    }

    uint32_t current_tick = system_get_tick();
    while(system_get_tick() - current_tick <= resp->timeout)
    {
        if(_serial->readable() > 0)
        {
            _serial->read(0,&ch,1,NULL);
            rev_buf[rev_count++] = ch;
            if(rev_count >= AT_LINE_SIZE_MAX)
            {
                rev_count = 0;
                LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
                return -2;
            }

            if((urc = _at_get_urc_obj(rev_buf,rev_count)) != NULL)
            {
                if (urc->func != NULL)
                {
                    LogDebug("%s execute urc %s\r\n",__FUNCTION__,urc->cmd_prefix);
                    urc->func(_serial, rev_buf, rev_count);
                    rev_count = 0;
                    continue;
                }
            }

            if((ch == '\n')&&(last_ch == '\r'))
            {
                if(rev_count <= 2)
                {
                    rev_count = 0;
                    continue;
                }

                rev_count = rev_count -2;
                rev_buf[rev_count++] = '\0';
                if((resp->buf_len+rev_count > resp->buf_size)||(resp->line_offset + 1 > resp->line_max))
                {
                    LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
                    return -2;
                }
                LogDebug("%s line_%d = %s\r\n",__FUNCTION__,resp->line_counts,rev_buf);

                memcpy(resp->buf + resp->buf_len,rev_buf,rev_count);
                resp->line[resp->line_offset++] = resp->buf + resp->buf_len;
                resp->buf_len += rev_count;
                rev_count = 0;
                resp->line_counts++;
                if(resp->line_num == 0)
                {
                    if((strcmp(rev_buf,AT_RESP_END_OK) == 0)||(strstr(rev_buf,AT_RESP_END_ERROR)))
                    {
                        LogDebug("%s exit with %s\r\n",__FUNCTION__,rev_buf);
                        break;
                    }
                }
                else
                {
                    if(resp->line_counts >= resp->line_num)
                    {
                        LogDebug("%s exit with line_counts = %d\r\n",__FUNCTION__,resp->line_counts);
                        break;
                    }
                }
            }
            last_ch = ch;
        }
        else
        {
            system_delay_ms(10);
        }
    }
    if(resp->line_counts == 0)
    {
        LogDebug("%s timeout\r\n",__FUNCTION__);
    }
    return resp->line_counts;
}

at_response_t at_create_resp(uint32_t buf_size, uint32_t line_num, uint32_t line_max, uint32_t timeout)
{
    at_response_t resp = NULL;

    resp = (at_response_t)at_malloc(sizeof(struct at_response));
    if(resp == NULL)
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return NULL;
    }

    resp->buf = (char *)at_malloc(buf_size);
    if(resp->buf == NULL)
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        at_free(resp);
        return NULL;
    }

    if(line_max <= 0)
    {
        line_max = 5;
    }
    resp->line = (char **)at_malloc(line_max*sizeof(char *));
    if(resp->line == NULL)
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        at_free(resp->buf);
        at_free(resp);
        return NULL;
    }

    resp->buf_size = buf_size;
    resp->line_num = line_num;
    resp->line_counts = 0;
    resp->timeout = timeout;
    resp->line_max = line_max;
    resp->line_offset = 0;

    return resp;
}

void at_delete_resp(at_response_t resp)
{
    if (resp && resp->buf)
    {
        at_free(resp->buf);
    }

    if(resp && resp->line)
    {
        at_free(resp->line);
    }

    if (resp)
    {
        at_free(resp);
        resp = NULL;
    }
}

char *at_resp_get_line(at_response_t resp,uint32_t resp_line)
{
    if(resp == NULL)
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return NULL;
    }
    if(resp_line >= resp->line_counts)
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return NULL;
    }
    return resp->line[resp_line];
}

char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword)
{
    if((resp == NULL)||(keyword == NULL))
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return NULL;
    }
    for(int i = 0; i < resp->line_counts; i++)
    {
        if (strstr(resp->line[i], keyword))
        {
            return resp->line[i];
        }
    }
    return NULL;
}

void at_clinet_tick(uint32_t delayms)
{
    const struct at_urc *urc;
    uint8_t ch = 0;

    uint32_t current_tick = 0;
    current_tick = system_get_tick();
    while(system_get_tick() - current_tick <= delayms)
    {
        if(_serial->readable() > 0)
        {
            _serial->read(0,&ch,1,NULL);
            rev_buf[rev_count++] = ch;
            if(rev_count >= AT_LINE_SIZE_MAX)
            {
                LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
                rev_count = 0;
                return;
            }
            if((urc = _at_get_urc_obj(rev_buf,rev_count)) != NULL)
            {
                if (urc->func != NULL)
                {
                    LogDebug("%s execute urc %s\r\n",__FUNCTION__,urc->cmd_prefix);
                    urc->func(_serial, rev_buf, rev_count);
                    rev_count = 0;
                }
            }
        }
        else
        {
            system_delay_ms(10);
        }
    }
}

int at_exec_cmd(at_response_t resp, const char *cmd_expr, ...)
{
    va_list args;

    if((resp == NULL)||(resp->buf == NULL))
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return -1;
    }

    resp->buf_len = 0;
    resp->line_counts = 0;

    va_start(args, cmd_expr);
    _at_vprintfln(cmd_expr,args);
    va_end(args);

    return _at_get_response(resp);
}

int at_exec_cmdstr(at_response_t resp, char *cmdstr, uint16_t cmdlen)
{
    if((resp == NULL)||(resp->buf == NULL))
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return -1;
    }

    resp->buf_len = 0;
    resp->line_counts = 0;

    _serial->write(0,cmdstr,cmdlen,NULL);

    return _at_get_response(resp);
}

void at_client_init(void *dev)
{
    _serial = (SERIAL_t)dev;
    _serial->ioctl(CMD_REV_INIT,NULL);
}

void at_client_flush(void)
{
    _serial->ioctl(CMD_REV_INIT,NULL);
}

int at_set_urc_table(const struct at_urc *urc_table, uint32_t table_sz)
{
    if((urc_table == NULL)||(table_sz == 0))
    {
        LogError("%s %d error\r\n",__FUNCTION__,__LINE__);
        return -1;
    }

    _urc_table.urc = urc_table;
    _urc_table.urc_size = table_sz;

    return 0;
}

void *at_get_dev(void)
{
    return _serial;
}


